在上一篇中,我們使用了純客戶端渲染(CSR)的方式來討論前端與 Opentelemetry 的關係。本篇文章中我們將進一步討論如何在 Next.js 這樣的全端框架中,實現 服務端渲染(SSR) 和 客戶端渲染(CSR) 的可觀測性,來涵蓋服務端和瀏覽器端的追蹤。
按照之前我們的demo,如果是後端服務的話,我們必須要在入口文件中先對 opentelemetry node sdk 進行初始化,讓它能夠先執行所宣告的 instrumentation、覆蓋目標方法,才能進行追蹤。
那麼在 NextJS 中,要在哪裡宣告?在目前版本中,可以以下步驟:
next.config.js
中開啟 instrumentationHook
設定:const nextConfig = {
experimental: {
instrumentationHook: true,
},
};
/app
同級的目錄下(App Router),新增 instrumentation.js
或instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
// nodejs runtime logic
console.log("---instrumentation in node runtime")
}else{
console.log("---instrumentation in other runtime")
}
}
運行後,可以看到在編譯頁面之前,console 輸出我們定義的文字:
代表這一段script可以執行在 node run time、而且是 server render 之前。
從之前的demo我們已經知道,server side 和 client side(browser)需要不同的 instrumentation,所以在全端框架如 NextJS 中,需要分別設定和分別運行。
新增一個 instrument.node.js
,把之前 demo 過的 NodeJS 中otel.js
直接複製過來(不過要改成 ES Module 格式):
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { OTLPTraceExporter } from
...
export const initNodeOtel = () => {
const resource = ...
const logExporter = ...
const metricExporter = ...
const traceExporter = ...
const otelSdk = new NodeSDK({
// SDK 設定
});
otelSdk.start();
};
然後在上一步創建的instrumentation.js
/instrumentation.ts
中,確認在 node runtime 的情況下再引入並呼叫,不然會報環境錯誤:
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
console.log('---instrumentation in node runtime');
const { initNodeOtel } = await import('./lib/otel/instrument.node');
initNodeOtel()
}
}
同時可以注意,在 getNodeAutoInstrumentations
中最好另外設定 @opentelemetry/instrumentation-fs
和 @opentelemetry/instrumentation-http
,因為 NextJS 在路由呼叫的時候已經有許多框架內的邏輯(如fs
、靜態資源獲取等等),會讓 trace 記錄擴增太多:
進入首頁後的服務端渲染 trace 記錄
針對客戶端監控,我們可以基於之前在 React 中的demo,修改其中的 otel.js
、並重命名為instrument.browser.js
:
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import {
BatchSpanProcessor,
SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-web';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
...
export const initBrowserOtel = () => {
const resource = new Resource({ ... });
const traceProvider = new WebTracerProvider({ resource });
const traceExporter = new OTLPTraceExporter({
url: exporterEndpoint.trace,
});
const spanProcessor = new SimpleSpanProcessor(traceExporter);
traceProvider.addSpanProcessor(spanProcessor);
// Register the provider
traceProvider.register();
// Auto-instrumentations
registerInstrumentations({
instrumentations: [
getWebAutoInstrumentations(),
new FetchInstrumentation({
propagateTraceHeaderCorsUrls: /.*/,
}),
],
tracerProvider: traceProvider,
});
};
接著我們要探討在何處運行這個 client side otel 的初始化。在之前的 React demo 中,我們是放在main.js
之類的入口文件的最上層,進行全局攔截覆蓋;但在 NextJS 中,我們需要在 js 被傳到 browser 後的執行階段再運行,也就是客戶端渲染的情況下再運行 initBrowserOtel()
因此,我們可以直接新增一個 client render component,然後讓其被掛載後在進行初始化:
'use client';
import React from 'react';
import { initBrowserOtel } from '../lib/otel/instrument.browser';
export default function Otel() {
React.useEffect(() => {
initBrowserOtel();
}, []);
return null;
}
然後可以透過 NextJS 的 Layout 機制(layout.js
),把<Otel/>
掛載到全局,也就是在 root layout 中使用:
export default function RootLayout({ children }) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Otel />
{children}
</body>
</html>
);
}
我們新增一個 api,然後在客戶端頁面呼叫該 api endpoint,測試一下測試 OpenTelemetry 的tracing:
app/api/demo/route.js
)export async function GET(req, res) {
await sleep(5000);
const data = 'success';
return Response.json({ data });
}
app/demo/page.js
)'use client';
import React from 'react';
export default function Demo() {
const clickHandler = async () => {
const res = await fetch('/api/demo', { method: 'GET' });
const data = await res.json();
console.log(data);
};
return (
<div>
<h1>Demo Page</h1>
<button onClick={() => clickHandler()}>demo api</button>
</div>
);
}
可以看到 Jaeger 成功記錄從 NextJS client side 到 server side 的鏈路追蹤。
這篇文章展示了如何在 NextJS 中實現全端的可觀測性。無論是 server-side 還是 client-side,我們都可以通過 OpenTelemetry 來進行監控和追蹤,幫助我們快速定位和解決問題。在服務端渲染(SSR)的架構中,我們介紹了如何基於 instrumentationHook
來實現 server-side 的 instrumentation;而在 client-side,我們通過通過 Web SDK 和 NextJS layout 機制來實現前端的 instrumentation。
本文完整程式碼可以在此 Github repository 中查看。